package com.atguigu.gmall.common.config;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.entity.GmallCorrelationData;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author 李旭
 * @date 2021/10/25 14:35
 * @Description:
 * MQProducerAckConfig :  MQ的生产者应答配置类
 *
 * SpringIOC容器  配置式开发   .xml
 *
 * <bean id=”mQProducerAckConfig”  class=”com.atguigu.gmall.mq.config.MQProducerAckConfig/>
 *
 * <bean id=”456”  class=”org.springframework.amqp.rabbit.core.RabbitTemplate>
 *       <property   name=ConfirmCallback  ref=”mQProducerAckConfig”> 注入
 *       <property   name=ReturnCallback ref=”mQProducerAckConfig”>
 * </bean>
 */
@Component  //注解开发
@Slf4j
public class MQProducerAckConfig implements RabbitTemplate.ConfirmCallback
                                           ,RabbitTemplate.ReturnCallback{
    /**
     * 1、MQProducerAckConfig被实例化了 @Component  放入springIOC容器中
     * 2、  //构造器
     *     public MQProducerAckConfig(){
     * 3、    //注入
     *     @Autowired
     *     private RabbitTemplate rabbitTemplate;
     *4、后置
     *    Bean实例化  前置  后置
     *    4.1 前置内容
     *    4.2 Bean实例化   注入
     *    4.3 后置内容  @PostConstruct  注解
     */
/*    @Resource
    private RabbitTemplate rabbitTemplate;*/
    //注入
    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    private RedisTemplate redisTemplate;
    //后置
    @PostConstruct
    public void MQProducerAckConfig(){
        rabbitTemplate.setConfirmCallback(this);
        rabbitTemplate.setReturnCallback(this);
    }


    //交换机应答  成功也应答 失败也应答
    //boolean ack 是否成功 , String cause  失败的原因
    // CorrelationData correlation  Data  相关联的数据
    @Override
    public void confirm(CorrelationData correlationData, boolean ack, String cause) {
        if(ack){
            //成功应答
            log.info("交换机成功接收到了消息:" + JSONObject.toJSONString(correlationData));
        }else{
            //失败应答
            log.error("交换机没有接收到消息,原因是：" + cause);
            //重新发消息 3次   指定次数  不允许无限消息   如果指定次数还是发不成功 打日志转人工处理
            reTrySendMessage(correlationData);

        }

    }

    //问题： 1、想要的数据没有
            //1、交换机
            //2、RoutingKey
            //3、消息
            //4、发送的次数
    //问题：2、CorrelationData ：是哪里来的
    //重新发消息 3次   指定次数  不允许无限消息   如果指定次数还是发不成功 打日志转人工处理
    private void reTrySendMessage(CorrelationData correlationData) {
        GmallCorrelationData gmallCorrelationData = (GmallCorrelationData)correlationData;

        //判断发送次数 不能超过3次
        int retryCount = gmallCorrelationData.getRetryCount();
        if(retryCount < 3){
            retryCount++;// 0-1 -2 -3
            //设置重发次数
            gmallCorrelationData.setRetryCount(retryCount);


            //更新Redis缓存中数据
            redisTemplate.opsForValue().set(gmallCorrelationData.getId(),
                    JSONObject.toJSONString(gmallCorrelationData));

            //重新发消息
            rabbitTemplate.convertAndSend(gmallCorrelationData.getExchange(),
                    gmallCorrelationData.getRoutingKey(),gmallCorrelationData.getMessage(),
                    gmallCorrelationData);//
        }else{
            //发送的次数已经耗尽了  {} : 占位符
         /*   log.error("发送的次数已经耗尽了:{}:{}" ,JSONObject.toJSONString(gmallCorrelationData)
              ,"haha");*/
             log.error("发送的次数已经耗尽了:{}" ,JSONObject.toJSONString(gmallCorrelationData));
        }



    }

    //队列应答  只有失败才应答
    @Override
    public void returnedMessage(Message message, int replyCode,
                                String replyText, String exchange, String routingKey) {

        log.error("队列失败应答");
        //log.error("消费的内容：" + new String(message.getBody()));// Long  UserRecode
        log.error("交换机是：" + exchange);
        log.error("routingKey是：" + routingKey);
        log.error("replyCode是：" + replyCode);
        log.error("replyText是：" + replyText);


        //队列应答失败 重新发消息 3次
        //交换机
        //RoutingKey
        //消息
        //重发的次数  无法记录  无限发送
        //办法1： 使用外力  使用Redis缓存来帮助我们设置重发的次数
        //办法2： Message
        String uuid = (String) message.getMessageProperties().
                getHeaders().get("spring_returned_message_correlation");
        log.info("队列应答失败了：uuid:" + uuid);
        //使用Redis获取gmallCorrelationData
        String json = (String) redisTemplate.opsForValue().get(uuid);
        log.info("队列应答失败了：json:" + json);

        //准备出来关联数据对象 GmallCorrelationData
        GmallCorrelationData gmallCorrelationData = JSONObject.parseObject(json, GmallCorrelationData.class);
        //判断是否为延迟消息
        if(!gmallCorrelationData.isDelay()){
            //重新发消息
            reTrySendMessage(gmallCorrelationData);
        }else{
            log.info("是延迟消息触发的队列应答失败：可以选择不重新发送: 交换机：{},RoutingKey：{}",
                    exchange,
                    routingKey);
            //万一：是延迟消息 发送之后 是真的失败了 而不是因为延迟在交换机上 从而造成队列应答失败
            //此小BUG 你能接受吗？ 能接受就使用插件的延迟消息  不能接收 同学使用死信的延迟消息
        }

    }
}
